#define __LIBRARY__
#include <unistd.h>
#include <time.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>
#include <stddef.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <string.h>

// #define RAMDISK 256

inline int fork(void) __attribute__((always_inline));
inline int pause(void) __attribute__((always_inline));
inline _syscall0(int, fork);
inline _syscall0(int, pause);
_syscall1(int, setup, void *, BIOS);
_syscall0(int, sync);

struct drive_info
{
    char dummy[32];
} drive_info;

static char printbuf[1024];

extern char *strcpy();
extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm *tm);
extern unsigned long startup_time;

static int sprintf(char *str, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsprintf(str, fmt, args);
    va_end(args);
    return i;
}

#define EXT_MEM_K (*(unsigned short *)0x90002)
#define CON_ROWS ((*(unsigned short *)0x9000e) & 0xff)
#define CON_COLS (((*(unsigned short *)0x9000e) & 0xff00) >> 8)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901fc)
// #define ORIG_SWAP_DEV (*(unsigned short *)0x901fa)

#define CMOS_READ(addr) ({ \
outb_p(0x80|(addr), 0x70); \
inb_p(0x71); })

#define BCD_TO_BIN(v) ((v) = ((v)&15) + ((v) >> 4) * 10)

static void time_init()
{
    struct tm time;
    do
    {
        time.tm_sec = CMOS_READ(0);
        time.tm_min = CMOS_READ(2);
        time.tm_hour = CMOS_READ(4);
        time.tm_mday = CMOS_READ(7);
        time.tm_mon = CMOS_READ(8) - 1;
        time.tm_year = CMOS_READ(9);
    } while (time.tm_sec != CMOS_READ(0));
    BCD_TO_BIN(time.tm_sec);
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    startup_time = kernel_mktime(&time);
}

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;
static char term[32];

static char * argv_init[] = { "/bin/init", NULL };
static char * envp_init[] = { "HOME=/", NULL, NULL };

static char *argv_rc[] = {"/bin/sh", NULL};
static char *envp_rc[] = {"HOME=/", NULL, NULL};
static char *argv[] = {"-/bin/sh", NULL};
static char *envp[] = {"HOME=/usr/root", NULL, NULL};

void start_kernel(void)
{
    ROOT_DEV = ORIG_ROOT_DEV;
    // SWAP_DEV = ORIG_SWAP_DEV;
    sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);
    envp[1] = term;
    envp_rc[1] = term;
    envp_init[1] = term;
    drive_info = DRIVE_INFO;

    memory_end = (1 << 20) + (EXT_MEM_K << 10);
    memory_end &= 0xfffff000;
    if (memory_end > 16 * 1024 * 1024)
        memory_end = 16 * 1024 * 1024;

    if (memory_end >= 12 * 1024 * 1024)
        buffer_memory_end = 4 * 1024 * 1024;
    else if (memory_end >= 6 * 1024 * 1024)
        buffer_memory_end = 2 * 1024 * 1024;
    else
        buffer_memory_end = 1 * 1024 * 1024;
    main_memory_start = buffer_memory_end;
#ifdef RAMDISK
    main_memory_start += rd_init(main_memory_start, RAMDISK * 1024);
#endif

    mem_init(main_memory_start, memory_end);
    trap_init();
    blk_dev_init();
    tty_init();
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
    hd_init();
    floppy_init();
    sti();
    move_to_user_mode();

    if (!fork()) {
        init();
    }
    

    for (;;)
        pause();
}

int printf(const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    write(1, printbuf, i = vsprintf(printbuf, fmt, args));
    va_end(args);
    return i;
}

void init(void)
{
    int pid, i, fd;

    setup((void *)&drive_info);
    
    (void)open("/dev/tty1", O_RDWR, 0);
    (void)dup(0);
    (void)dup(0);

    // execve("/etc/init",argv_init,envp_init);
	// execve("/bin/init",argv_init,envp_init);
	/* if this fails, fall through to original stuff */

    if (!(pid = fork()))
    {
        close(0);
        if (open("/etc/rc", O_RDONLY, 0))
            _exit(1);
        execve("/bin/sh", argv_rc, envp_rc);
        _exit(2);
    }
    printf("\n\t\thello treeos!\n\n");

    if (pid > 0)
        while (pid != wait(&i))
            ;

    while (1)
    {
        if ((pid = fork()) < 0)
        {
            continue;
        }
        if (!pid)
        {
            close(0);
            close(1);
            close(2);
            setsid();
            (void)open("/dev/tty1", O_RDWR, 0);
            (void)dup(0);
            (void)dup(0);
            _exit(execve("/bin/sh", argv, envp));
        }
        while (1)
            if (pid == wait(&i))
                break;
        sync();
    }
     
    _exit(0);
}